﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Romulus.Nes;
using SolKeyTest.Data;

namespace SolKeyTest.Data
{
    public enum KeyState
    {
        Normal = 0x01,
        HiddenInBlock = 0x40,
        Hidden = 0x80
    }

    public class Level
    {
        const int keyStatusOffset = 4;
        const int doorPositionOffset = 5;
        const int keyPositionOffset = 6;
        const int playerStartOffset = 7;
        const int spawn1Offset = 8;
        const int spawn2Offset = 9;
        const int itemSetOffset = 10;

        // in the item set, 0xC(n) where N is a hex number means the same item is used in the next (n) positions.
        const int itemRepeat = 0xC0;

        // zodiac (0xF(n)) with a position and the delimiter (0xE(y)) are the last values in the item list
        // the lower byte (n or y, respectively) is used to determine which tileset the level uses.
        const int zodiacItem = 0xF0;
        const int itemDelimiter = 0xE0;

        public int LevelNumber { get; private set; }
        public byte PlayerStart { get; set; }
        public byte Exit { get; set; }
        public byte KeyPosition { get; set; }
        public KeyState KeyStatus { get; set; }
        public IList<SolKeyLevelElement> ItemsList { get; set; }
        public IList<SolKeyLevelElement> EnemiesList { get; set; }
        public byte[] MagicBlocks { get; private set; }
        public byte[] SolidBlocks { get; private set; }
        public byte SpawnPos1 { get; set; }
        public byte SpawnPos2 { get; set; }
        public int TileSetIndex { get; private set; }

        public byte[] Items { get { return null; } }
        public byte[] Enemies { get { return null; } }

        // Each level may have a different background tile set and background palette
        public PatternTable BackgroundPatterns { get; private set; }
        public CompositePalette BackgroundPalette { get; private set; }
        private SolRom SolKeyRom { get; set; }

        // Helper to expose Rom as our specific ROM type.
        public Level(int LevelNum, SolRom Rom, int PaletteIndex)
        {
            this.LevelNumber = LevelNum;
            this.SolKeyRom = Rom;
            this.ItemsList = new List<SolKeyLevelElement>();

            // Load the item list
            LoadItemListFromROM();

            this.BackgroundPatterns = Rom.bgPatterns[TileSetIndex];
            // Load the enemy list
            LoadEnemyList();
            // Load the magic blocks
            MagicBlocks = new byte[24];
            LoadBlocksFromROM(MagicBlocks, false);
            // Load the solid blocks
            SolidBlocks = new byte[24];
            LoadBlocksFromROM(SolidBlocks, true);
        }

        public bool ValidateLevel()
        {
            // Each level must have a start position, key, and door in valid spots.
            return true;
        }

        private void LoadBlocksFromROM(byte[] blockSet, bool SolidBlocks)
        {
            // Start at the ROM's block offset + (levelNum - 1) * 2 * length.
            // The next 24 bytes define the preloaded magic blocks
            int blockOffset = SolidBlocks ? SolKeyRom.LevelBlockLength : 0;
            pHRom startPos = SolKeyRom.LevelBlockOffset + (this.LevelNumber - 1) * 2 * SolKeyRom.LevelBlockLength + blockOffset;
            pHRom endPos = startPos + SolKeyRom.LevelBlockLength;
            int blockIndex = 0;
            for (int romOffset = startPos.Offset; romOffset < endPos.Offset; romOffset++)
            {
                blockSet[blockIndex++] = SolKeyRom.data[romOffset];
            }
        }

        private void LoadEnemyList()
        {
            return;
        }

        private void LoadItemListFromROM()
        {
            // In the ROM, the "item" list also has positions of the player start, exit,
            // enemy spawns, zodiac painting, and background tile set.
            // Load all of these here, but only add actual items to the item list.
            byte[] ItemArray = GetItemArrayFromROM();
            this.SpawnPos1 = ItemArray[spawn1Offset];
            this.SpawnPos2 = ItemArray[spawn2Offset];
            this.Exit = ItemArray[doorPositionOffset];
            this.KeyPosition = ItemArray[keyPositionOffset];

            // the last element is either the position of the zodiac or the item delimiter
            byte patternByte = ItemArray[ItemArray.Length - 1];
            if (!IsItemDelimiter(patternByte))
            {
                // if the last item was not the delimiter (wasn't like 0xe(n)), get the byte before
                patternByte = ItemArray[ItemArray.Length - 2];
            }
            TileSetIndex = 0;
            if ((patternByte & 0x04) == 4)
                TileSetIndex = 1;
            else if ((patternByte & 0x08) == 8)
                TileSetIndex = 2;

            // Iterate through the rest of the item array to load items into the level's collection
            for (int i = itemSetOffset; i < ItemArray.Length; i++)
            {
                byte itemType = ItemArray[i];
                int repeatCount = 0;
                if (!IsItemDelimiter(itemType) && !IsItemZodiac(itemType))
                {
                    // Special rule for 0xC(n)
                    // 0xC(n) means that the next item will repeat n times.
                    // Example: C1 18 45 A5
                    //    The above set means that item 18 (bell) will be at position 45 and A5
                    // Commentary:  This is a stupid trick to save few bytes in the ROM, but back in 1987 it was necessary.
                    if (IsItemRepeater(itemType))
                    {
                        repeatCount = itemType ^ itemRepeat;
                        itemType = ItemArray[++i];
                    }
                    do
                    {
                        this.ItemsList.Add(new SolKeyLevelElement(ElementTypes.Item, itemType, ItemArray[++i]));
                        repeatCount--;
                    } while (repeatCount >= 0);
                }
                else
                    return;
            }
        }

        private bool IsItemZodiac(byte p)
        {
            return (p & 0xF0) == zodiacItem;
        }

        private bool IsItemRepeater(byte p)
        {
            return (p & itemRepeat) == itemRepeat;
        }

        private bool IsItemDelimiter(byte p)
        {
            return (p & 0xF0) == itemDelimiter;
        }

        private byte[] GetItemArrayFromROM()
        {
            int itemAddress = GetAddressByPointers(SolKeyRom.ItemPointerHighOffset, SolKeyRom.ItemPointerLowOffset).Offset;
            List<Byte> itemsList = new List<Byte>();
            do
            {
                itemsList.Add(SolKeyRom.data[itemAddress++]);
            } while (!IsItemDelimiter(SolKeyRom.data[itemAddress]) && !IsItemZodiac(SolKeyRom.data[itemAddress]));
            if ((SolKeyRom.data[itemAddress] & 0xF0) == zodiacItem)
            {
                // Add the zodiac item and get ready to add the last byte
                itemsList.Add(SolKeyRom.data[itemAddress++]);
            }

            // Add last byte.  It is either the delimiter or the position of the zodiac
            itemsList.Add(SolKeyRom.data[itemAddress]);

            return itemsList.ToArray();
        }

        private pHRom GetAddressByPointers(pHRom HiByte, pHRom LowByte)
        {
            byte hi = SolKeyRom.data[HiByte.Offset + this.LevelNumber - 1];
            byte lo = SolKeyRom.data[LowByte.Offset + this.LevelNumber - 1];
            pHRom pointerOffset = new pHRom((hi << 8) + lo);

            return new pHRom(pointerOffset.Offset - 0x8000 + 0x10);
        }


    }
}
